package org.nielin.bestwaper.core;


import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

import org.nielin.util.ClassLoaderUtil;
import org.nielin.util.log.MyLogger;
import org.nielin.util.xml.DomHelper;
import org.nielin.util.location.Location;


public final class DispatcherMap implements Manageable
{
    private DispatcherMap() {
        try
        {
            loadDispatcher();
        }
        catch(Exception e)
        {
            MyLogger.getFatal().fatal("DispatcherMap init aid error", e);
            
            // try again
            try {
                loadDispatcher();
            } catch (Exception e1) {
                MyLogger.getFatal().fatal("DispatcherMap init aid error again!", e);
            }
        }
    }

    private static DispatcherMap instance = new DispatcherMap();
    
    public static DispatcherMap getInstance() {
        return instance;
    }
    
    private static final String DISPATCHER_FILE_PATH = "/dispatch/";
	private static final String DISPATCHER_FILE_SUFFIX_NAME = "_dispatcher.xml";
	
	private Map<String,WebEntity> dispatcherMap = new ConcurrentHashMap<String,WebEntity>();	// id与实体的对应关系
    private Map<String,WebEntity> dispatcherMapTmp;   // load时临时存放id与实体的对应关系
	
    @InvokeByManager public void loadDispatcher() throws Exception
	{
        dispatcherMapTmp = new ConcurrentHashMap<String,WebEntity>();
		Map<String,String> aliasMap = new ConcurrentHashMap<String,String>();
        
        URL u = ClassLoaderUtil.getResource(DISPATCHER_FILE_PATH, DispatcherMap.class);
        File dispatcherConfDir = null;
        try {
            dispatcherConfDir = new File(u.toURI());
        } catch (URISyntaxException e) {
        	MyLogger.getNormal().error( "new File[" + DISPATCHER_FILE_PATH + "] fail", e );
        }
        
        if ( dispatcherConfDir == null || !dispatcherConfDir.isDirectory() )
        {
            MyLogger.getNormal().error( "dispatcher conf path[" + DISPATCHER_FILE_PATH + "] is not a directory!");
            throw new Exception( "dispatcher conf path[" + DISPATCHER_FILE_PATH + "] is not a directory!" );
        }
        
        FilenameFilter filenameFilter = new FilenameFilter() {
            public boolean accept(File dir, String name) {
                if ( name.endsWith(DispatcherMap.DISPATCHER_FILE_SUFFIX_NAME) ) {
                    return true;
                }
                else
                {
                    return false;
                }
            }    
        };
        
        File[] dispatcherFiles = dispatcherConfDir.listFiles( filenameFilter );       
        
        for (File file : dispatcherFiles) {
            MyLogger.getNormal().debug("get file:" + file.getName());
            
            InputStream is = new FileInputStream(file);
            InputSource in = new InputSource(is);
            Document dispatcherDoc = DomHelper.parse( in );

            Element root = dispatcherDoc.getDocumentElement();
            NodeList list = root.getChildNodes();
            for (int i = 0; i < list.getLength(); i++) {
                Node node = list.item(i);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    MyLogger.getNormal().debug("node:" + node.getNodeName());
                    if (node.getNodeName().equals("view")) {
                        Element views = (Element) node;
                        NodeList packages = views.getChildNodes();
                        for (int j = 0; j < packages.getLength(); j++) {
                            Node onepackage = packages.item(j);
                            if (onepackage.getNodeType() == Node.ELEMENT_NODE) {
                                Element onepackageElement = (Element) onepackage;
                                String packageSpace = onepackageElement
                                        .getAttribute("space").trim();
                                NodeList pages = onepackageElement
                                        .getChildNodes();
                                for (int k = 0; k < pages.getLength(); k++) {
                                    Node page = pages.item(k);
                                    if (page.getNodeType() == Node.ELEMENT_NODE) {
                                        Element pageElement = (Element) page;
                                        String id = pageElement
                                                .getAttribute("id").trim();
                                        if (id == null || id.length() == 0 ) {
                                            Location location = DomHelper.getLocationObject(pageElement);
                                            MyLogger.getFatal().fatal("id has not configed! The location is:" + location.toString());
                                            continue;
                                        }

                                        String configedUrl = pageElement
                                                .getAttribute("url").trim();
                                        String url;
                                        if (configedUrl != null && configedUrl.length() > 0) {
                                            url = packageSpace + "/"
                                                    + configedUrl;
                                        } else {
                                            url = packageSpace + "/" + id
                                                    + ".jsp";
                                        }
                                        String alias = pageElement
                                                .getAttribute("alias").trim();

                                        // alias和url同时配置时，优先使用alias
                                        if (alias != null && alias.length() > 0) {
                                            // 记录配置了alias的view
                                            if (aliasMap.get(id) != null) {
                                                // 发现id冲突
                                                Location location = DomHelper.getLocationObject(pageElement);
                                                MyLogger.getFatal().fatal(
                                                                "id["
                                                                        + id
                                                                        + "] has existed, not added! The location is:" +location.toString());
                                            } else {
                                                aliasMap.put(id, alias);
                                            }
                                        } else {
                                            WebEntity entity = new View(id, url);

                                            if (dispatcherMapTmp.get(id) != null) {
                                                // 发现id冲突
                                                Location location = DomHelper.getLocationObject(pageElement);
                                                MyLogger.getFatal().fatal(
                                                                "id["
                                                                        + id
                                                                        + "] has existed, not added! The location is:" +location.toString());
                                            } else {
                                                dispatcherMapTmp
                                                        .put(id, entity);
                                            }
                                            MyLogger.getNormal().debug(
                                                    "add id[" + id + "]"
                                                            + " view[id=" + id
                                                            + ",url=" + url
                                                            + "]");
                                        }
                                    }
                                }
                            }
                        }
                    }

                    if (node.getNodeName().equals("controller")) {
                        Element controllers = (Element) node;
                        NodeList packages = controllers.getChildNodes();
                        for (int j = 0; j < packages.getLength(); j++) {
                            Node onepackage = packages.item(j);
                            if (onepackage.getNodeType() == Node.ELEMENT_NODE) {
                                Element onepackageElement = (Element) onepackage;
                                String packageSpace = onepackageElement
                                        .getAttribute("space").trim();
                                NodeList actions = onepackageElement
                                        .getChildNodes();
                                for (int k = 0; k < actions.getLength(); k++) {
                                    Node action = actions.item(k);
                                    if (action.getNodeType() == Node.ELEMENT_NODE) {
                                        Element actionElement = (Element) action;
                                        String id = actionElement
                                                .getAttribute("id").trim();
                                        String className = actionElement.getAttribute("class").trim();
                                        if ( className == null || className.length() == 0 ) {
                                            // 将id的首字母大写当做class
                                            className = id.substring(0,1).toUpperCase() + id.substring(1);
                                        }
                                        className = packageSpace
                                                + "."
                                                + className;
                                        Map<String, String> resultMap = new ConcurrentHashMap<String, String>();
                                        String resultMapStr = "";
                                        NodeList results = actionElement
                                                .getChildNodes();
                                        for (int l = 0; l < results.getLength(); l++) {
                                            Node result = results.item(l);
                                            if (result.getNodeName().equals(
                                                    "result")
                                                    && result.getNodeType() == Node.ELEMENT_NODE) {
                                                Element resultElement = (Element) result;
                                                String status = resultElement
                                                        .getAttribute("status").trim();
                                                String destId = resultElement
                                                        .getAttribute("destId").trim();
                                                if (destId.equals(id)) {
                                                    // 嵌套调用，这样可能陷入死循环，非常危险，提示！只检测了一层的情况，多层的情况没有检测
                                                    Location location = DomHelper.getLocationObject(resultElement);
                                                    MyLogger
                                                            .getNormal()
                                                            .error(
                                                                    "id["
                                                                            + id
                                                                            + "] dest to destId["
                                                                            + destId
                                                                            + "]! The location is:" + location.toString());
                                                }
                                                resultMap.put(status, destId);
                                                resultMapStr += ("status="
                                                        + status + ",destId="
                                                        + destId + "|");
                                            }
                                        }

                                        WebEntity entity = null;
                                        try {
                                            entity = new Controller(id,
                                                    className, resultMap);
                                        } catch (Exception e) {
                                            Location location = DomHelper.getLocationObject(actionElement);
                                            MyLogger.getFatal().fatal(
                                                    "instance Controller id["
                                                            + id
                                                            + "] className["
                                                            + className
                                                            + "] fail! The location is:" + location.toString(), e);
                                            continue;
                                        }

                                        if (dispatcherMapTmp.get(id) != null) {
                                            // 发现id冲突
                                            Location location = DomHelper.getLocationObject(actionElement);
                                            MyLogger
                                                    .getFatal()
                                                    .fatal(
                                                            "id["
                                                                    + id
                                                                    + "] has existed, not added! The location is:" + location.toString());
                                        } else {
                                            dispatcherMapTmp.put(id, entity);
                                        }
                                        MyLogger.getNormal().debug(
                                                "add id[" + id + "]"
                                                        + " controller[id="
                                                        + id + ",className="
                                                        + className
                                                        + ",resultMap="
                                                        + resultMapStr + "]");
                                    }
                                }
                            }
                        }

                    }
                }
            }
        }
        
        // 处理alias id
        Iterator<String> ite = aliasMap.keySet().iterator();
        while (ite.hasNext()) {
            String id = ite.next();
            String alias = aliasMap.get(id);
            WebEntity aliasEntity = dispatcherMapTmp.get(alias);
            if (aliasEntity == null) {
                MyLogger.getNormal().fatal(
                        "id[" + id + "]'s alias[" + alias
                                + "] is not existed!");
            } else {
                if (aliasEntity instanceof View) {
                    dispatcherMapTmp.put(id, aliasEntity);
                    View v = (View) aliasEntity;
                    MyLogger.getNormal().debug(
                            "add id[" + id + "]" + " view[aliasid="
                                    + id + ",url="
                                    + v.getForwardUrl() + "]");
                }
            }
        }        
	
        dispatcherMap = dispatcherMapTmp;
	}   
    
    /**
     * 根据id得到相应的实体
     * @param id
     * @return
     */
    public WebEntity getEntity(String id)
    {
        if ( dispatcherMap == null ) {
            return null;
        }
        
    	return dispatcherMap.get(id);
    }    
    
    public boolean checkConfigFileValidation( String errorMsg, String infoMsg ) throws Exception {
        Map<String,WebEntity> dispatcherMapCheck = new ConcurrentHashMap<String,WebEntity>();
        Map<String,String> aliasMapCheck = new ConcurrentHashMap<String,String>();
        
        URL u = ClassLoaderUtil.getResource(DISPATCHER_FILE_PATH, DispatcherMap.class);
        File dispatcherConfDir = null;
        try {
            dispatcherConfDir = new File(u.toURI());
        } catch (URISyntaxException e) {
            // swallow
        }
        
        if ( dispatcherConfDir == null || !dispatcherConfDir.isDirectory() )
        {
            MyLogger.getNormal().error( "dispatcher conf path[" + DISPATCHER_FILE_PATH + "] is not a directory!");
            throw new Exception( "dispatcher conf path[" + DISPATCHER_FILE_PATH + "] is not a directory!" );
        }
        
        FilenameFilter filenameFilter = new FilenameFilter() {
            public boolean accept(File dir, String name) {
                if ( name.endsWith(DispatcherMap.DISPATCHER_FILE_SUFFIX_NAME) ) {
                    return true;
                }
                else
                {
                    return false;
                }
            }    
        };
        
        File[] dispatcherFiles = dispatcherConfDir.listFiles( filenameFilter ); 

        for (File file : dispatcherFiles) {
            Document dispatcherDoc;
            try {
                InputStream is = new FileInputStream(file);
                InputSource in = new InputSource(is);
                in.setSystemId(file.getName());
                dispatcherDoc = DomHelper.parse(in);
            } catch (IOException e1) {
                errorMsg = "parse config file fail, exception:" + e1.getMessage();
                return false;
            }

            Element root = dispatcherDoc.getDocumentElement();
            NodeList list = root.getChildNodes();
            for (int i = 0; i < list.getLength(); i++) {
                Node node = list.item(i);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    if (node.getNodeName().equals("view")) {
                        Element views = (Element) node;
                        NodeList packages = views.getChildNodes();
                        for (int j = 0; j < packages.getLength(); j++) {
                            Node onepackage = packages.item(j);
                            if (onepackage.getNodeType() == Node.ELEMENT_NODE) {
                                Element onepackageElement = (Element) onepackage;
                                String packageSpace = onepackageElement
                                        .getAttribute("space").trim();
                                NodeList pages = onepackageElement
                                        .getChildNodes();
                                for (int k = 0; k < pages.getLength(); k++) {
                                    Node page = pages.item(k);
                                    if (page.getNodeType() == Node.ELEMENT_NODE) {
                                        Element pageElement = (Element) page;
                                        String id = pageElement
                                                .getAttribute("id").trim();
                                        if (id == null) {
                                            errorMsg = "id has not configed!";
                                            return false;
                                        }

                                        String configedUrl = pageElement
                                                .getAttribute("url").trim();
                                        String url;
                                        if (configedUrl != null && configedUrl.length() > 0) {
                                            url = packageSpace + "/"
                                                    + configedUrl;
                                        } else {
                                            url = packageSpace + "/" + id
                                                    + ".jsp";
                                        }
                                        String alias = pageElement
                                                .getAttribute("alias").trim();

                                        // alias和url同时配置时，优先使用alias
                                        if (alias != null && alias.length() > 0) {
                                            // 记录配置了alias的view
                                            if (aliasMapCheck.get(id) != null) {
                                                // 发现id冲突
                                                errorMsg = "id[" + id + "] has existed, not added!";
                                                return false;
                                            } else {
                                                aliasMapCheck.put(id, alias);
                                            }
                                        } else {
                                            WebEntity entity = new View(id, url);

                                            if (dispatcherMapCheck.get(id) != null) {
                                                // 发现id冲突
                                                errorMsg = "id[" + id + "] has existed, not added!";
                                                return false;
                                            } else {
                                                dispatcherMapCheck
                                                        .put(id, entity);
                                            }
                                            infoMsg += "add id[" + id + "]"
                                                            + " view[id=" + id
                                                            + ",url=" + url
                                                            + "]";
                                        }
                                    }
                                }
                            }
                        }
                    }

                    if (node.getNodeName().equals("controller")) {
                        Element controllers = (Element) node;
                        NodeList packages = controllers.getChildNodes();
                        for (int j = 0; j < packages.getLength(); j++) {
                            Node onepackage = packages.item(j);
                            if (onepackage.getNodeType() == Node.ELEMENT_NODE) {
                                Element onepackageElement = (Element) onepackage;
                                String packageSpace = onepackageElement
                                        .getAttribute("space").trim();
                                NodeList actions = onepackageElement
                                        .getChildNodes();
                                for (int k = 0; k < actions.getLength(); k++) {
                                    Node action = actions.item(k);
                                    if (action.getNodeType() == Node.ELEMENT_NODE) {
                                        Element actionElement = (Element) action;
                                        String id = actionElement
                                                .getAttribute("id").trim();
                                        String className = packageSpace
                                                + "."
                                                + actionElement
                                                        .getAttribute("class").trim();
                                        Map<String, String> resultMap = new ConcurrentHashMap<String, String>();
                                        String resultMapStr = "";
                                        NodeList results = actionElement
                                                .getChildNodes();
                                        for (int l = 0; l < results.getLength(); l++) {
                                            Node result = results.item(l);
                                            if (result.getNodeName().equals(
                                                    "result")
                                                    && result.getNodeType() == Node.ELEMENT_NODE) {
                                                Element resultElement = (Element) result;
                                                String status = resultElement
                                                        .getAttribute("status").trim();
                                                String destId = resultElement
                                                        .getAttribute("destId").trim();
                                                if (destId.equals(id)) {
                                                    // 嵌套调用，这样可能陷入死循环，非常危险，提示！只检测了一层的情况，多层的情况没有检测
                                                    errorMsg = "id["
                                                                            + id
                                                                            + "] dest to destId["
                                                                            + destId
                                                                            + "]!";
                                                    return false;
                                                }
                                                resultMap.put(status, destId);
                                                resultMapStr += ("status="
                                                        + status + ",destId="
                                                        + destId + "|");
                                            }
                                        }

                                        WebEntity entity = null;
                                        try {
                                            entity = new Controller(id,
                                                    className, resultMap);
                                        } catch (Exception e) {
                                            errorMsg = "instance Controller id["
                                                            + id
                                                            + "] className["
                                                            + className
                                                            + "] fail, exception:" + e.getMessage();
                                            return false;
                                        }

                                        if (dispatcherMapCheck.get(id) != null) {
                                            // 发现id冲突
                                            errorMsg = "id["
                                                                    + id
                                                                    + "] has existed, not added!";
                                        } else {
                                            dispatcherMapCheck.put(id, entity);
                                        }
                                        infoMsg +=
                                                "add id[" + id + "]"
                                                        + " controller[id="
                                                        + id + ",className="
                                                        + className
                                                        + ",resultMap="
                                                        + resultMapStr + "]";
                                    }
                                }
                            }
                        }

                    }
                }
            }
        }
        
        // 处理alias id
        Iterator<String> ite = aliasMapCheck.keySet().iterator();
        while (ite.hasNext()) {
            String id = ite.next();
            String alias = aliasMapCheck.get(id);
            WebEntity aliasEntity = dispatcherMapCheck.get(alias);
            if (aliasEntity == null) {
                errorMsg = "id[" + id + "]'s alias[" + alias
                                + "] is not existed!";
                return false;
            } else {
                if (aliasEntity instanceof View) {
                    dispatcherMapCheck.put(id, aliasEntity);
                    View v = (View) aliasEntity;
                    infoMsg += "add id[" + id + "]" + " view[aliasid="
                                    + id + ",url="
                                    + v.getForwardUrl() + "]";
                }
            }
        }
        
        return true;
    }
    
    public static void main(String[] args) {
        String errorMsg = new String("");
        String infoMsg = new String("");
        try {
            if ( DispatcherMap.getInstance().checkConfigFileValidation(errorMsg, infoMsg) ) {
                System.out.println( "check succ! info :" + infoMsg );
            }
            else {
                System.out.println( "check fail! info :" + errorMsg );
            }
        } catch (Exception e) {
            System.out.println( "check error!" );
            e.printStackTrace();
        }
    }
    
}
